﻿/* Copyright (C) 2008 - 2011 by John Cronin
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:

 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.

 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

using System;
using System.Collections.Generic;
using System.Text;

namespace tysila
{
    partial class x86_64_Assembler : LSB_Assembler
    {
        public bool emitNop = false;

        class x86_64_RegisterAllocator : IRegisterAllocator
        {
            Dictionary<x86_64_gpr.RegId, Nullable<var>> gprs = new Dictionary<x86_64_gpr.RegId, Nullable<var>>();
            Dictionary<x86_64_xmm.XmmId, Nullable<var>> xmms = new Dictionary<x86_64_xmm.XmmId, Nullable<var>>();

            const x86_64_gpr.RegId temp_reg = x86_64_gpr.RegId.rdi;

            public CodeBlock set_up_lvar_stack = null;

            public x86_64_RegisterAllocator()
            {
                gprs.Add(x86_64_gpr.RegId.rax, null);
                gprs.Add(x86_64_gpr.RegId.rcx, null);
                gprs.Add(x86_64_gpr.RegId.rdx, null);
                gprs.Add(x86_64_gpr.RegId.rbx, null);
                gprs.Add(x86_64_gpr.RegId.rsi, null);
                gprs.Add(x86_64_gpr.RegId.rdi, null);
                gprs.Add(x86_64_gpr.RegId.rbp, var.Const("RBP"));
                gprs.Add(x86_64_gpr.RegId.rsp, var.Const("RSP"));
                gprs.Add(x86_64_gpr.RegId.r8, null);
                gprs.Add(x86_64_gpr.RegId.r9, null);
                gprs.Add(x86_64_gpr.RegId.r10, null);
                gprs.Add(x86_64_gpr.RegId.r11, null);
                gprs.Add(x86_64_gpr.RegId.r12, null);
                gprs.Add(x86_64_gpr.RegId.r13, null);
                gprs.Add(x86_64_gpr.RegId.r14, null);
                gprs.Add(x86_64_gpr.RegId.r15, null);

                xmms.Add(x86_64_xmm.XmmId.xmm0, null);
                xmms.Add(x86_64_xmm.XmmId.xmm1, null);
                xmms.Add(x86_64_xmm.XmmId.xmm2, null);
                xmms.Add(x86_64_xmm.XmmId.xmm3, null);
                xmms.Add(x86_64_xmm.XmmId.xmm4, null);
                xmms.Add(x86_64_xmm.XmmId.xmm5, null);
                xmms.Add(x86_64_xmm.XmmId.xmm6, null);
                xmms.Add(x86_64_xmm.XmmId.xmm7, null);
                xmms.Add(x86_64_xmm.XmmId.xmm8, null);
                xmms.Add(x86_64_xmm.XmmId.xmm9, null);
                xmms.Add(x86_64_xmm.XmmId.xmm10, null);
                xmms.Add(x86_64_xmm.XmmId.xmm11, null);
                xmms.Add(x86_64_xmm.XmmId.xmm12, null);
                xmms.Add(x86_64_xmm.XmmId.xmm13, null);
                xmms.Add(x86_64_xmm.XmmId.xmm14, null);
                xmms.Add(x86_64_xmm.XmmId.xmm15, null);

                gprs[temp_reg] = var.Const("TempReg");
            }

            public void FreeCoercions()
            {
                FreeGprCoercion(x86_64_gpr.RegId.rax);
                FreeGprCoercion(x86_64_gpr.RegId.rbx);
                FreeGprCoercion(x86_64_gpr.RegId.rcx);
                FreeGprCoercion(x86_64_gpr.RegId.rdx);
                FreeGprCoercion(x86_64_gpr.RegId.rdi);
                FreeGprCoercion(x86_64_gpr.RegId.rsi);
                FreeGprCoercion(x86_64_gpr.RegId.rbp);
                FreeGprCoercion(x86_64_gpr.RegId.rsp);
                FreeGprCoercion(x86_64_gpr.RegId.r8);
                FreeGprCoercion(x86_64_gpr.RegId.r9);
                FreeGprCoercion(x86_64_gpr.RegId.r10);
                FreeGprCoercion(x86_64_gpr.RegId.r11);
                FreeGprCoercion(x86_64_gpr.RegId.r12);
                FreeGprCoercion(x86_64_gpr.RegId.r13);
                FreeGprCoercion(x86_64_gpr.RegId.r14);
                FreeGprCoercion(x86_64_gpr.RegId.r15);
                FreeXmmCoercion(x86_64_xmm.XmmId.xmm0);
                FreeXmmCoercion(x86_64_xmm.XmmId.xmm1);
                FreeXmmCoercion(x86_64_xmm.XmmId.xmm2);
                FreeXmmCoercion(x86_64_xmm.XmmId.xmm3);
                FreeXmmCoercion(x86_64_xmm.XmmId.xmm4);
                FreeXmmCoercion(x86_64_xmm.XmmId.xmm5);
                FreeXmmCoercion(x86_64_xmm.XmmId.xmm6);
                FreeXmmCoercion(x86_64_xmm.XmmId.xmm7);
                FreeXmmCoercion(x86_64_xmm.XmmId.xmm8);
                FreeXmmCoercion(x86_64_xmm.XmmId.xmm9);
                FreeXmmCoercion(x86_64_xmm.XmmId.xmm10);
                FreeXmmCoercion(x86_64_xmm.XmmId.xmm11);
                FreeXmmCoercion(x86_64_xmm.XmmId.xmm12);
                FreeXmmCoercion(x86_64_xmm.XmmId.xmm13);
                FreeXmmCoercion(x86_64_xmm.XmmId.xmm14);
                FreeXmmCoercion(x86_64_xmm.XmmId.xmm15);
            }

            private void FreeGprCoercion(x86_64_gpr.RegId regId)
            {
                if (gprs[regId].HasValue && gprs[regId].Value.IsCoerce())
                    gprs[regId] = null;
            }
            private void FreeXmmCoercion(x86_64_xmm.XmmId regId)
            {
                if (xmms[regId].HasValue && xmms[regId].Value.IsCoerce())
                    xmms[regId] = null;
            }

            private hardware_location GetSpecificXmm(x86_64_xmm x86_64_xmm, var var_id)
            {
                if (xmms[x86_64_xmm.xmm] == null)
                {
                    xmms[x86_64_xmm.xmm] = var_id;
                    return x86_64_xmm;
                }
                return null;
            }

            private hardware_location GetSpecificGpr(x86_64_gpr x86_64_gpr, var var_id)
            {
                if (gprs[x86_64_gpr.reg] == null)
                {
                    gprs[x86_64_gpr.reg] = var_id;
                    return x86_64_gpr;
                }
                return null;
            }

            private hardware_location GetNextXmm(var var_id)
            {
                foreach (KeyValuePair<x86_64_xmm.XmmId, Nullable<var>> val in xmms)
                {
                    if (val.Value == null)
                    {
                        xmms[val.Key] = var_id;
                        return new x86_64_xmm { xmm = val.Key };
                    }
                }
                return null;
            }

            private hardware_location GetNextGpr(var var_id)
            {
                foreach (KeyValuePair<x86_64_gpr.RegId, Nullable<var>> val in gprs)
                {
                    if (val.Value == null)
                    {
                        gprs[val.Key] = var_id;
                        return new x86_64_gpr { reg = val.Key };
                    }
                }
                return null;
            }


            public class x86_64_LocalVarHardwareAllocator : LocalVarArgHardwareAllocator
            {
                public class x86_64_NegHardwareStackAllocator : HardwareStackAllocator, IStackAllocator
                {
                    protected override bool AllocatesDownwards()
                    { return true; }
                    protected override int GetStackAlign()
                    { return 8; }
                }
                internal x86_64_NegHardwareStackAllocator stack_alloc = new x86_64_NegHardwareStackAllocator();

                protected override hardware_location AllocateHardwareLocation(var v, int size)
                {
                    return stack_alloc.GetNextStackLoc(v, size);         
                }

                public x86_64_LocalVarHardwareAllocator Clone()
                {
                    x86_64_LocalVarHardwareAllocator clone = new x86_64_LocalVarHardwareAllocator();
                    clone.stack_alloc = stack_alloc.Clone() as x86_64_NegHardwareStackAllocator;
                    return clone;
                }
            }

            public class x86_64_LocalArgHardwareAllocator : LocalVarArgHardwareAllocator
            {
                public class x86_64_PosHardwareStackAllocator : HardwareStackAllocator, IStackAllocator
                {
                    protected override bool AllocatesDownwards()
                    { return false; }
                    protected override int GetStackAlign()
                    { return 8; }
                    public x86_64_PosHardwareStackAllocator() { this.cur_stack_loc = 16; }
                }
                internal x86_64_PosHardwareStackAllocator stack_alloc = new x86_64_PosHardwareStackAllocator();

                protected override hardware_location AllocateHardwareLocation(var v, int size)
                {
                    return stack_alloc.GetNextStackLoc(v, size);
                }

                public x86_64_LocalArgHardwareAllocator Clone()
                {
                    x86_64_LocalArgHardwareAllocator clone = new x86_64_LocalArgHardwareAllocator();
                    clone.stack_alloc = stack_alloc.Clone() as x86_64_PosHardwareStackAllocator;
                    return clone;
                }
            }

            protected x86_64_LocalArgHardwareAllocator larg_alloc = new x86_64_LocalArgHardwareAllocator();
            protected x86_64_LocalVarHardwareAllocator lvar_alloc = new x86_64_LocalVarHardwareAllocator();

            public LocalVarArgHardwareAllocator GetLocalVarAllocator() { return lvar_alloc; }
            public LocalVarArgHardwareAllocator GetLogicalVarAllocator() { return lvar_alloc; }
            public LocalVarArgHardwareAllocator GetLocalArgAllocator() { return larg_alloc; }

            public IRegisterAllocator Clone()
            {
                x86_64_Assembler.x86_64_RegisterAllocator clone = new x86_64_RegisterAllocator();

                clone.gprs = new Dictionary<x86_64_gpr.RegId, var?>(this.gprs);
                clone.xmms = new Dictionary<x86_64_xmm.XmmId, var?>(this.xmms);
                clone.lvar_alloc = this.lvar_alloc.Clone();
                clone.larg_alloc = this.larg_alloc.Clone();

                return clone;
            }

            public IEnumerable<hardware_location> AllOfType(System.Type type)
            {
                if (type == typeof(x86_64_gpr))
                {
                    yield return x86_64_Assembler.Rax;
                    yield return x86_64_Assembler.Rbx;
                    yield return x86_64_Assembler.Rcx;
                    yield return x86_64_Assembler.Rdx;
                    yield return x86_64_Assembler.Rsi;
                    yield return x86_64_Assembler.Rdi;
                    yield return x86_64_Assembler.R8;
                    yield return x86_64_Assembler.R9;
                    yield return x86_64_Assembler.R10;
                    yield return x86_64_Assembler.R11;
                    yield return x86_64_Assembler.R12;
                    yield return x86_64_Assembler.R13;
                    yield return x86_64_Assembler.R14;
                    yield return x86_64_Assembler.R15;
                    yield break;
                }
                else if (type == typeof(x86_64_xmm))
                {
                    yield return x86_64_Assembler.Xmm0;
                    yield return x86_64_Assembler.Xmm1;
                    yield return x86_64_Assembler.Xmm2;
                    yield return x86_64_Assembler.Xmm3;
                    yield return x86_64_Assembler.Xmm4;
                    yield return x86_64_Assembler.Xmm5;
                    yield return x86_64_Assembler.Xmm6;
                    yield return x86_64_Assembler.Xmm7;
                    yield return x86_64_Assembler.Xmm8;
                    yield return x86_64_Assembler.Xmm9;
                    yield return x86_64_Assembler.Xmm10;
                    yield return x86_64_Assembler.Xmm11;
                    yield return x86_64_Assembler.Xmm12;
                    yield return x86_64_Assembler.Xmm13;
                    yield return x86_64_Assembler.Xmm14;
                    yield return x86_64_Assembler.Xmm15;
                    yield break;
                }
                else
                    throw new NotSupportedException();
            }
        }
        
        protected override IRegisterAllocator GetRegisterAllocator()
        {
            return new x86_64_RegisterAllocator();
        }

        private List<ThreeAddressCode.Op> exclude_from_twoaddress = new List<ThreeAddressCode.Op>
        {
            ThreeAddressCode.Op.rem_i,
            ThreeAddressCode.Op.rem_i4,
            ThreeAddressCode.Op.rem_i8,
            ThreeAddressCode.Op.rem_un_i,
            ThreeAddressCode.Op.rem_un_i4,
            ThreeAddressCode.Op.rem_un_i8,
            ThreeAddressCode.Op.div_i,
            ThreeAddressCode.Op.div_i4,
            ThreeAddressCode.Op.div_i8,
            ThreeAddressCode.Op.div_u,
            ThreeAddressCode.Op.div_u4,
            ThreeAddressCode.Op.div_u8
        };

        protected override void ArchSpecific(List<ThreeAddressCode> ir, List<cfg_node> nodes, AssemblerState state)
        {
            foreach (cfg_node node in nodes)
            {
                ConvertToTwoAddress(node.optimized_ir, exclude_from_twoaddress);
                node.live_vars_at_end.Clear();
                node.live_vars_done = false;

                if (node.optimized_ir != null)
                {
                    for (int i = 0; i < node.optimized_ir.Count; i++)
                    {
                        ThreeAddressCode inst = node.optimized_ir[i];
                        // insert workarounds for instructions which we cannot encode

                        switch (inst.Operator)
                        {
                            case ThreeAddressCode.Op.conv_u4_r8:
                                node.optimized_ir[i] = new CallEx(inst.Result, new var[] { inst.Operand1 }, "_conv_u4_r8", callconv_conv_u4_r8);
                                break;
                            case ThreeAddressCode.Op.conv_u8_r8:
                            case ThreeAddressCode.Op.conv_u_r8:
                                node.optimized_ir[i] = new CallEx(inst.Result, new var[] { inst.Operand1 }, "_conv_u8_r8", callconv_conv_u8_r8);
                                break;
                            case ThreeAddressCode.Op.zeromem:
                                {
                                    switch ((int)inst.Operand2.constant_val)
                                    {
                                        case 1:
                                            node.optimized_ir[i] = new ThreeAddressCode(ThreeAddressCode.Op.poke_u1, var.Null, inst.Operand1, var.Const((byte)0));
                                            break;
                                        case 2:
                                            node.optimized_ir[i] = new ThreeAddressCode(ThreeAddressCode.Op.poke_u2, var.Null, inst.Operand1, var.Const((UInt16)0));
                                            break;
                                        case 4:
                                            node.optimized_ir[i] = new ThreeAddressCode(ThreeAddressCode.Op.poke_u4, var.Null, inst.Operand1, var.Const((UInt32)0));
                                            break;
                                        case 8:
                                            node.optimized_ir[i] = new ThreeAddressCode(ThreeAddressCode.Op.poke_u8, var.Null, inst.Operand1, var.Const((UInt64)0));
                                            break;
                                    }
                                }
                                break;
                        }

                        //Un-nest instructions that reference [la/v1 + x] to a = la/v1, b = [a + x]
                        if ((inst.Operand1.type == var.var_type.ContentsOf || inst.Operand1.type == var.var_type.ContentsOfPlusConstant) &&
                            (inst.Operand1.base_var.v.type == var.var_type.LocalArg || inst.Operand1.base_var.v.type == var.var_type.LocalVar))
                        {
                            var intermediate = state.next_variable++;
                            node.optimized_ir.Insert(i, new ThreeAddressCode(ThreeAddressCode.Op.assign_i, intermediate, inst.Operand1.base_var.v, var.Null));
                            inst.Operand1.base_var.v = intermediate;
                            i++;
                        }
                        if ((inst.Operand2.type == var.var_type.ContentsOf || inst.Operand2.type == var.var_type.ContentsOfPlusConstant) &&
                            (inst.Operand2.base_var.v.type == var.var_type.LocalArg || inst.Operand2.base_var.v.type == var.var_type.LocalVar))
                        {
                            var intermediate = state.next_variable++;
                            node.optimized_ir.Insert(i, new ThreeAddressCode(ThreeAddressCode.Op.assign_i, intermediate, inst.Operand2.base_var.v, var.Null));
                            inst.Operand2.base_var.v = intermediate;
                            i++;
                        }
                        if ((inst.Result.type == var.var_type.ContentsOf || inst.Result.type == var.var_type.ContentsOfPlusConstant) &&
                            (inst.Result.base_var.v.type == var.var_type.LocalArg || inst.Result.base_var.v.type == var.var_type.LocalVar))
                        {
                            var intermediate = state.next_variable++;
                            node.optimized_ir.Insert(i, new ThreeAddressCode(ThreeAddressCode.Op.assign_i, intermediate, inst.Result.base_var.v, var.Null));
                            inst.Result.base_var.v = intermediate;
                            i++;
                        }
                        if (inst is CallEx)
                        {
                            CallEx ce = inst as CallEx;
                            for (int j = 0; j < ce.Var_Args.Length; j++)
                            {
                                if ((ce.Var_Args[j].type == var.var_type.ContentsOf || ce.Var_Args[j].type == var.var_type.ContentsOfPlusConstant) &&
                                    (ce.Var_Args[j].base_var.v.type == var.var_type.LocalArg || ce.Var_Args[j].base_var.v.type == var.var_type.LocalVar))
                                {
                                    var intermediate = state.next_variable++;
                                    node.optimized_ir.Insert(i, new ThreeAddressCode(ThreeAddressCode.Op.assign_i, intermediate, ce.Var_Args[j].base_var.v, var.Null));
                                    ce.Var_Args[j].base_var.v = intermediate;
                                    i++;
                                }
                            }
                        }
                    }
                }
            }

            // Repeat liveness analysis for new code
            LivenessAnalysis(InsertPseudoEnd(nodes, state), nodes);

            ir.Clear();
            foreach (cfg_node node in nodes)
            {
                if(node.optimized_ir != null)
                    ir.AddRange(node.optimized_ir);
            }
        }

        private void ConvertToTwoAddress(List<ThreeAddressCode> ir, List<ThreeAddressCode.Op> excludes)
        {
            int i = 0;
            if (ir == null)
                return;
            while (i < ir.Count)
            {
                ThreeAddressCode.OpType optype = ir[i].GetOpType();

                if (((optype == ThreeAddressCode.OpType.BinNumOp) || (optype == ThreeAddressCode.OpType.UnNumOp)) && (!excludes.Contains(ir[i].Operator)))
                {
                    // we need to convert a line of the form v1 = v2 op v3 to:
                    //  v1 = v2, v1 = v1 op v3

                    if (ir[i].Result.logical_var != ir[i].Operand1.logical_var)
                    {
                        var v1 = ir[i].Result.CloneVar();
                        var v2 = ir[i].Operand1.CloneVar();
                        var v3 = ir[i].Operand2.CloneVar();

                        ir.Insert(i, new ThreeAddressCode
                        {
                            Operator = GetAssignTac(ir[i].GetResultType()),
                            Result = v1,
                            Operand1 = v2,
                            Operand2 = var.Undefined
                        });

                        ir[i + 1].Operand1 = v1;
                        i++;
                    }
                }
                i++;
            }
        }

        public override int GetSizeOf(Signature.Param p)
        {
            switch (p.CliType(this))
            {
                case CliType.F64:
                    return 8;
                case CliType.F32:
                    return 4;
                case CliType.int32:
                    return 4;
                case CliType.int64:
                    return 8;
                case CliType.native_int:
                    return 8;
                case CliType.O:
                    return 8;
                case CliType.reference:
                    return 8;
                case CliType.void_:
                    return 8;
                case CliType.vt:
                    return GetSizeOfType(p);
                case CliType.virtftnptr:
                    return 16;
                default:
                    throw new NotSupportedException();
            }
        }
        public override int GetPackedSizeOf(Signature.Param p)
        {
            if (p.Type is Signature.BaseType)
            {
                Signature.BaseType bt = p.Type as Signature.BaseType;
                switch (bt.Type)
                {
                    case BaseType_Type.I1:
                    case BaseType_Type.Byte:
                    case BaseType_Type.Boolean:
                    case BaseType_Type.U1:
                        return 1;
                    case BaseType_Type.Char:
                    case BaseType_Type.I2:
                    case BaseType_Type.U2:
                        return 2;
                }
            }
            return GetSizeOf(p);
        }
        private List<byte> EncOpcode(int r, int rm, int mod, byte sib, bool rexw, int disp, params byte[] opcodes)
        { return EncOpcode(r, rm, mod, sib, rexw, disp, false, opcodes); }

        private List<byte> EncOpcode(int r, int rm, int mod, byte sib, bool rexw, int disp, bool rm8, params byte[] opcodes)
        {
            if (opcodes.Length == 0)
                throw new Exception("opcodes is empty");

            List<byte> ret = new List<byte>();
            byte rex = 0x0;
            if (rm8)
            {
                if ((rm == (int)Rdi.reg) || (rm == (int)Rsi.reg))
                    rex |= Rex(true);
            }
            if(rexw)
                rex |= RexW(true);

            if (r >= 8)
                rex |= RexR(true);
            if (rm >= 8)
                rex |= RexB(true);

            /* 0xf2,0xf3,0x66,0xf0 prefixes comes before rex */
            if ((opcodes.Length > 0) && ((opcodes[0] == 0xf2) || (opcodes[0] == 0xf3) || (opcodes[0] == 0x66) || (opcodes[0] == 0xf0)))
                ret.Add(opcodes[0]);
            if (rex != 0x0)
                ret.Add(rex);
            foreach (byte b in opcodes)
            {
                if ((b != 0xf2) && (b != 0xf3) && (b != 0x66) && (b != 0xf0))
                    ret.Add(b);
            }
            if ((mod == 0x0) || (mod == 0x1) || (mod == 0x2))
            {
                if ((disp == 0) && (rm != 4))       // [rsp] must be encoded with an sib byte
                    mod = 0x0;
                else if ((disp < SByte.MinValue) || (disp > SByte.MaxValue))
                    mod = 0x2;
                else
                    mod = 0x1;
            }
            ret.Add(ModRM((byte)mod, (byte)(r % 8), (byte)(rm % 8)));
            if (((mod == 0x1) || (mod == 0x2)) && (rm == 0x4))
                ret.Add(sib);

            if (mod == 0x1)
                ret.AddRange(ToByteArray(Convert.ToSByte(disp)));
            else if (mod == 0x2)
                ret.AddRange(ToByteArray(Convert.ToInt32(disp)));
            return ret;
        }
        private List<byte> EncOpcode(object r, object rm, int mod, bool rexw, int disp,
            params byte[] opcodes)
        { return EncOpcode(r, rm, mod, rexw, disp, false, opcodes); }

        private List<byte> EncOpcode(object r, object rm, int mod, bool rexw, int disp, bool rm8,
            params byte[] opcodes)
        {
            int rmval = 0;
            int rval = 0;
            byte sib = 0;
            if (rm is hardware_stackloc)
            {
                mod = 1;
                disp = (int)((uint)(-((hardware_stackloc)rm).loc - ((hardware_stackloc)rm).size) & 0xfffffff8);
                rm = new x86_64_gpr { reg = x86_64_gpr.RegId.rbp };
                rmval = (int)((x86_64_gpr)rm).reg;
            }
            else if (rm is hardware_contentsof)
            {
                hardware_contentsof hco = rm as hardware_contentsof;

                if (hco.base_loc is hardware_stackloc)
                {
                    //throw new Exception("Shouldn't get here");
                    mod = 1;
                    disp = -((hardware_stackloc)hco.base_loc).loc - ((hardware_stackloc)hco.base_loc).size + hco.const_offset;
                    rm = Rbp;
                    rmval = (int)((x86_64_gpr)rm).reg;
                }
                else if (hco.base_loc is hardware_contentsof)
                {
                    throw new Exception("Shouldn't get here");
                    /*mod = 1;
                    disp = ((hardware_contentsof)hco.base_loc).const_offset + hco.const_offset;
                    rmval = (int)((x86_64_gpr)(((hardware_contentsof)hco.base_loc).base_loc)).reg;*/
                }
                else
                {
                    mod = 1;
                    disp = hco.const_offset;
                    rm = hco.base_loc;
                    rmval = (int)((x86_64_gpr)rm).reg;
                }

                if (rmval == 4)
                    sib = 0x24;
            }
            else if (rm.GetType() == typeof(int))
            {
                rmval = (int)rm;
            }
            else if (rm is x86_64_gpr)
                rmval = (int)((x86_64_gpr)rm).reg;
            else if (rm is x86_64_xmm)
                rmval = (int)((x86_64_xmm)rm).xmm;
            else
                throw new NotSupportedException();

            if (r is int)
                rval = (int)r;
            else if (r is x86_64_gpr)
                rval = (int)((x86_64_gpr)r).reg;
            else if (r is x86_64_xmm)
                rval = (int)((x86_64_xmm)r).xmm;
            else
                throw new NotSupportedException();

            return EncOpcode(rval, rmval, mod, sib, rexw, disp, rm8, opcodes);
        }
        private List<byte> EncAddOpcode(x86_64_gpr reg, bool rexw, byte opcode, params byte[] immediates)
        {
            List<byte> ret = new List<byte>();
            byte rex = RexW(rexw);
            rex |= RexB(reg.is_extended);
            if (rex != 0)
                ret.Add(rex);
            ret.Add((byte)(opcode + reg.base_val));
            foreach (byte b in immediates)
                ret.Add(b);
            return ret;
        }

        /*struct TempReg { public x86_64_gpr reg; public x86_64_gpr saved_reg; }
        private TempReg GetExplicitTempReg(IList<OutputBlock> obs, x86_64_gpr preferred, AssemblerState state)
        {
            hardware_location ret = state.reg_alloc.GetRegister(new hloc_constraint
            {
                constraint = hloc_constraint.c_.Specific,
                specific = preferred
            }, var.Const("tempreg"));

            if (ret != null)
                return new TempReg { reg = ret as x86_64_gpr, saved_reg = null };

            obs.Add(new CodeBlock { Code = SaveLocation(preferred) });
            return new TempReg { reg = preferred, saved_reg = preferred };
        }
        private TempReg GetTempReg(IList<OutputBlock> obs, AssemblerState state, params x86_64_gpr[] avoid_regs)
        {
            hardware_location ret = state.reg_alloc.GetRegister(CGpr, var.Const("tempreg"));

            if(ret != null)
                return new TempReg { reg = ret as x86_64_gpr, saved_reg = null };

            List<x86_64_gpr> check_order = new List<x86_64_gpr> { Rdx, Rcx, Rax, Rbx, Rdi, Rsi, R8, R9, R10, R11,
                R12, R13, R14, R15 };
            foreach (x86_64_gpr gpr in check_order)
            {
                bool can_use = true;
                foreach (x86_64_gpr avoid in avoid_regs)
                {
                    if (gpr.Equals(avoid))
                    {
                        can_use = false;
                        break;
                    }
                }
                if (can_use)
                {
                    return GetExplicitTempReg(obs, gpr, state);
                }
            }
            throw new Exception();
        }
        private void FreeTempReg(IList<OutputBlock> obs, TempReg tr, AssemblerState state)
        {
            if (tr.saved_reg == null)
                state.reg_alloc.FreeRegister(tr.reg);
            else
                obs.Add(new CodeBlock { Code = RestoreLocation(tr.saved_reg) });
        } */

        private byte ModRM(byte mod, byte reg, byte rm)
        { return (byte)((mod << 6) | (reg << 3) | (rm)); }

        private byte Rex(bool present)
        {
            if (present)
                return 0x40;
            else
                return 0x00;
        }

        private byte RexW(bool present)
        {
            if (present)
                return 0x48;
            else
                return 0x00;
        }

        private byte RexR(bool present)
        {
            if (present)
                return 0x44;
            else
                return 0x00;
        }

        private byte RexX(bool present)
        {
            if (present)
                return 0x42;
            else
                return 0x00;
        }

        private byte RexB(bool present)
        {
            if (present)
                return 0x41;
            else
                return 0x00;
        }

        /*protected override hardware_location GetLocalArgLocation(var v)
        {
            return new hardware_stackloc { loc = 16 + (v.local_arg * 8) };
        }
        protected override hardware_location GetLocalVarLocation(var v)
        {
            throw new NotImplementedException();
            return new hardware_stackloc { loc = -1 - v.local_var };
        }*/

        public override List<byte> SwapLocation(hardware_location a, hardware_location b)
        {
            List<byte> r = new List<byte>();
            if ((a is x86_64_gpr) && (b is x86_64_gpr))
            {
                // XCHG
                r.AddRange(EncOpcode(a, b, 3, true, 0, 0x87));
            }
            else
                throw new NotImplementedException();

            return r;
        }

        protected override List<byte> SaveLocation(hardware_location loc)
        {
            List<byte> b = new List<byte>();
            if (loc is x86_64_gpr)
            {
                x86_64_gpr gpr = loc as x86_64_gpr;
                if (gpr.is_extended)
                    b.Add(RexB(true));
                b.Add((byte)(0x50 + gpr.base_val));
            }
            else if (loc is x86_64_xmm)
            {
                // decrement stack counter then store to [rsp]
                b.AddRange(new byte[] { 0x48, 0x83, 0xec, 0x08 });
                b.AddRange(EncOpcode(loc, new hardware_contentsof { base_loc = Rsp }, 0, false, 0, 0x66, 0x0f, 0xd6));  // MOVQ
            }
            else
                throw new NotImplementedException();

            return b;
        }

        protected override List<byte> RestoreLocation(hardware_location loc)
        {
            List<byte> b = new List<byte>();
            if (loc is x86_64_gpr)
            {
                x86_64_gpr gpr = loc as x86_64_gpr;
                if (gpr.is_extended)
                    b.Add(RexB(true));
                b.Add((byte)(0x58 + gpr.base_val));
            }
            else if (loc is x86_64_xmm)
            {
                // restore from [rsp] then increment stack counter
                b.AddRange(EncOpcode(loc, new hardware_contentsof { base_loc = Rsp }, 0, false, 0, 0xf3, 0x0f, 0x7e));  // MOVQ
                b.AddRange(new byte[] { 0x48, 0x83, 0xc4, 0x08 });
            }
            else
                throw new NotImplementedException();

            return b;
        }

        public override uint DataToDataRelocType()
        {
            return ElfLib.Elf64_Rela_Shdr.Elf64_Rela.RelocationType.R_X86_64_64;
        }

        public override byte[] IntPtrByteArray(object v)
        {
            return ToByteArray(Convert.ToInt64(v));
        }

        protected override object ConvertToI(object v)
        {
            return Convert.ToInt64(v);
        }

        protected override object ConvertToU(object v)
        {
            return Convert.ToUInt64(v);
        }

        internal override string GetCType(BaseType_Type baseType_Type)
        {
            switch (baseType_Type)
            {
                case BaseType_Type.Byte:
                case BaseType_Type.U1:
                case BaseType_Type.Boolean:
                    return "uint8_t";

                case BaseType_Type.Char:
                case BaseType_Type.U2:
                    return "uint16_t";

                case BaseType_Type.U4:
                    return "uint32_t";

                case BaseType_Type.I1:
                    return "int8_t";

                case BaseType_Type.I2:
                    return "int16_t";

                case BaseType_Type.I4:
                    return "int32_t";

                case BaseType_Type.I:
                case BaseType_Type.I8:
                    return "int64_t";

                case BaseType_Type.Object:
                case BaseType_Type.String:
                case BaseType_Type.U:
                case BaseType_Type.U8:
                    return "uint64_t";

                default:
                    throw new NotImplementedException();
            }
        }

        public override int GetSizeOfUncondBr()
        {
            return 5;
        }

        public override IntPtr FromByteArrayI(IList<byte> v)
        {
            return new IntPtr(FromByteArrayI8(v));
        }
        public override IntPtr FromByteArrayI(IList<byte> v, int offset)
        {
            return new IntPtr(FromByteArrayI8(v, offset));
        }
        public override UIntPtr FromByteArrayU(IList<byte> v)
        {
            return new UIntPtr(FromByteArrayU8(v));
        }
        public override UIntPtr FromByteArrayU(IList<byte> v, int offset)
        {
            return new UIntPtr(FromByteArrayU8(v, offset));
        }

        protected override void ArchSpecificCode(AssemblerState state, IList<OutputBlock> blocks)
        {
            // Insert an x86_64 prolog

            int offset = 0;
            blocks.Insert(offset++, new CodeBlock(new byte[] { 0x55 }, new x86_64_Instruction { opcode = "push", Operand1 = Rbp }));
            blocks.Insert(offset++, new CodeBlock(new byte[] { 0x48, 0x89, 0xe5 }, new x86_64_Instruction { opcode = "mov", Operand1 = Rsp, Operand2 = Rbp }));

            if (state.stack_space_used > 0)
            {
                if (FitsSByte(state.stack_space_used))
                {
                    blocks.Insert(offset++, new CodeBlock(EncOpcode(5, Rsp, 3, true, 0, 0x83), ToByteArraySignExtend(state.stack_space_used, 1),
                        new x86_64_Instruction[] { new x86_64_Instruction { opcode = "sub", Operand1 = Rsp, Operand2 = new const_location { c = state.stack_space_used } } }));
                }
                else if (FitsInt32(state.stack_space_used))
                {
                    blocks.Insert(offset++, new CodeBlock(EncOpcode(5, Rsp, 3, true, 0, 0x81), ToByteArraySignExtend(state.stack_space_used, 4),
                        new x86_64_Instruction[] { new x86_64_Instruction { opcode = "sub", Operand1 = Rsp, Operand2 = new const_location { c = state.stack_space_used } } }));
                }
                else
                    throw new NotSupportedException();
            }

            if (((x86_64_AssemblerState)state).isr)
            {
                // For an ISR we need to save all the registers (there is no pushad command on x86_64)
                blocks.Insert(offset++, new CodeBlock(EncAddOpcode(Rax, false, 0x50), new x86_64_Instruction { opcode = "push", Operand1 = Rax }));
                blocks.Insert(offset++, new CodeBlock(EncAddOpcode(Rbx, false, 0x50), new x86_64_Instruction { opcode = "push", Operand1 = Rbx }));
                blocks.Insert(offset++, new CodeBlock(EncAddOpcode(Rcx, false, 0x50), new x86_64_Instruction { opcode = "push", Operand1 = Rcx }));
                blocks.Insert(offset++, new CodeBlock(EncAddOpcode(Rdx, false, 0x50), new x86_64_Instruction { opcode = "push", Operand1 = Rdx }));
                blocks.Insert(offset++, new CodeBlock(EncAddOpcode(Rdi, false, 0x50), new x86_64_Instruction { opcode = "push", Operand1 = Rdi }));
                blocks.Insert(offset++, new CodeBlock(EncAddOpcode(Rsi, false, 0x50), new x86_64_Instruction { opcode = "push", Operand1 = Rsi }));
                blocks.Insert(offset++, new CodeBlock(EncAddOpcode(R8, false, 0x50), new x86_64_Instruction { opcode = "push", Operand1 = R8 }));
                blocks.Insert(offset++, new CodeBlock(EncAddOpcode(R9, false, 0x50), new x86_64_Instruction { opcode = "push", Operand1 = R9 }));
                blocks.Insert(offset++, new CodeBlock(EncAddOpcode(R10, false, 0x50), new x86_64_Instruction { opcode = "push", Operand1 = R10 }));
                blocks.Insert(offset++, new CodeBlock(EncAddOpcode(R11, false, 0x50), new x86_64_Instruction { opcode = "push", Operand1 = R11 }));
                blocks.Insert(offset++, new CodeBlock(EncAddOpcode(R12, false, 0x50), new x86_64_Instruction { opcode = "push", Operand1 = R12 }));
                blocks.Insert(offset++, new CodeBlock(EncAddOpcode(R13, false, 0x50), new x86_64_Instruction { opcode = "push", Operand1 = R13 }));
                blocks.Insert(offset++, new CodeBlock(EncAddOpcode(R14, false, 0x50), new x86_64_Instruction { opcode = "push", Operand1 = R14 }));
                blocks.Insert(offset++, new CodeBlock(EncAddOpcode(R15, false, 0x50), new x86_64_Instruction { opcode = "push", Operand1 = R15 }));
            }

            if (state.syscall)
            {
                // Syscalls on the x86_64 platform are currently non-interruptible
                blocks.Insert(offset++, new CodeBlock(new byte[] { 0x9c }, new x86_64_Instruction { opcode = "pushfq" }));
                blocks.Insert(offset++, new CodeBlock(new byte[] { 0xfa }, new x86_64_Instruction { opcode = "cli" }));
            }

            if (state.stack_space_used > 0)
            {
                /* Initialise the local variables
                 * 
                 * Mov rcx, stack space / 8
                 * clear rax
                 * lea rdi, [rbp - stack_space]
                 * rep stosq
                 */

                blocks.Insert(offset++, new CodeBlock(EncOpcode(0, Rcx, 3, true, 0, 0xc7), ToByteArraySignExtend((state.stack_space_used / 8), 4), new CodeBlock.CompiledInstruction[] { new x86_64_Instruction { opcode = "mov", Operand1 = Rcx, Operand2 = new const_location { c = state.stack_space_used / 8 } } }));
                blocks.Insert(offset++, new CodeBlock(EncOpcode(Rax, Rax, 3, true, 0, 0x31), new x86_64_Instruction { opcode = "xor", Operand1 = Rax, Operand2 = Rax }));
                blocks.Insert(offset++, new CodeBlock(EncOpcode(Rdi, new hardware_contentsof { base_loc = Rbp, const_offset = -state.stack_space_used }, 0, true, 0, 0x8d), new x86_64_Instruction { opcode = "lea", Operand1 = Rdi, Operand2 = new hardware_contentsof { base_loc = Rbp, const_offset = -state.stack_space_used } }));
                blocks.Insert(offset++, new CodeBlock(new byte[] { 0xf3, 0x48, 0xab }, new x86_64_Instruction { opcode = "rep stosq" }));

            }
        }

        public class x86_64_AssemblerState : AssemblerState
        {
            public bool isr = false;
        }

        protected override AssemblerState GetNewAssemblerState()
        {
            return new x86_64_AssemblerState();
        }

        protected override void InterpretMethodCustomAttribute(MethodToCompile mtc, Metadata.CustomAttributeRow car, AssemblerState state)
        {
            Metadata.MethodDefRow camdr = Metadata.GetMethodDef(car.Type.ToToken(), this);
            string caname = Mangler.Mangle(camdr, this);
            caname = caname.Substring(Mangler.Mangle(camdr.m).Length);
            if (caname == "_N3_ABI_T12_ISRAttribute_Special_M7_#2Ector_S6_200001")
            {
                ((x86_64_AssemblerState)state).isr = true;
                state.call_conv = "isr";
            }
        }
    }
}
